package edu.iscas.xcheck.analysis.handler;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import edu.iscas.xcheck.analysis.dom.checker.ErrorReporter;
import edu.iscas.xcheck.analysis.dom.checker.IsoChecker;
import edu.iscas.xcheck.analysis.dom.checker.XBIResult;
import edu.iscas.xcheck.analysis.dom.model.BrowserModel;
import edu.iscas.xcheck.analysis.dom.model.BrowserPage;
import edu.iscas.xcheck.analysis.dom.model.StateEdge;
import edu.iscas.xcheck.analysis.dom.model.TestCaseStateEdge;
import edu.iscas.xcheck.analysis.dom.utils.BrowserLogComparator;
import edu.iscas.xcheck.analysis.dom.utils.DataProcessor;
import edu.iscas.xcheck.analysis.dom.utils.StateEdgeUtil;
import edu.iscas.xcheck.analysis.info.DirInfo;
import edu.iscas.xcheck.analysis.info.InternalServerConfig;
import edu.iscas.xcheck.analysis.model.AnalysisConfig;
import edu.iscas.xcheck.analysis.model.AnalysisInfo;
import edu.iscas.xcheck.analysis.model.AnalysisInfo.AnalysisStatus;
import edu.iscas.xcheck.analysis.model.AnalysisLog;
import edu.iscas.xcheck.analysis.model.AnalysisResult;
import edu.iscas.xcheck.analysis.model.Platform;
import edu.iscas.xcheck.analysis.model.PlatformXbi;
import edu.iscas.xcheck.analysis.model.StateIssue;
import edu.iscas.xcheck.analysis.service.RegisterClient;
import edu.iscas.xcheck.utils.http.PostClient;

public class AnalysisRunner implements Runnable {

	private static final Logger LOG = LoggerFactory
			.getLogger(AnalysisRunner.class);

	private AnalysisConfig config;
	private Map<Platform, PlatformXbi> platXbiMap;
	public AnalysisRunner(AnalysisConfig analysisConfig) {
		// TODO Auto-generated constructor stub
		this.config = analysisConfig;
		platXbiMap = new HashMap<>();
	}

	@Override
	public void run() {
		try {
			StateEdgeUtil seUtil = loadStateMap(config.getTestCaseId());
			// Create state map for each browser
			seUtil.addBrowser(config.getRefPlatform());
			for(Platform testPlat:config.getTestPlatform()){
				seUtil.addBrowser(testPlat);
			}
			AnalysisLog[] als = config.getLogs();
			for (int i = 0; i < als.length; i++) {
				AnalysisLog al = als[i];
				analysisLog(al.getLogId(), seUtil);
			}
			AnalysisResult results = new AnalysisResult(config.getTestCaseId());
			results.getXbi().addAll(platXbiMap.values());
			//上传结果
			uploadXBIResult(results);
			// post analysis info to xcheck web server
			
			postAnalysisInfo(new AnalysisInfo(config.getTestCaseId(), AnalysisStatus.FINISHED));
		} catch (HttpException e) {
			e.printStackTrace();
			postAnalysisInfo(new AnalysisInfo(config.getTestCaseId(), AnalysisStatus.FAILED));
		} catch (IOException e) {
			postAnalysisInfo(new AnalysisInfo(config.getTestCaseId(), AnalysisStatus.FAILED));
		} catch (Exception e){
			e.printStackTrace();
			postAnalysisInfo(new AnalysisInfo(config.getTestCaseId(), AnalysisStatus.FAILED));
		}
	}

	private void postAnalysisInfo(AnalysisInfo analysisInfo){
		PostClient postClient = new PostClient(RegisterClient.getHost(),RegisterClient.getPort(),RegisterClient.getPostPath());
		postClient.setJsonContent(analysisInfo);
		try {
			postClient.sendRequest();
		} catch (IOException e) {
			e.printStackTrace();
			LOG.error("Failed to send Analysis result to x-check web server");
		}
	}
	private void analysisLog(String logId, StateEdgeUtil seUtil) throws HttpException, IOException {

		// 1: Get Replay Log from Server and load stateMap
		Map<Platform, List<BrowserPage>> browserMap = loadReplayLog(logId);

		// 2: Data Preprocess for Lower browser
		long start = new Date().getTime();
		DataProcessor.processData(browserMap);

		// 3: Check
		List<BrowserPage> refBrowserPages = browserMap.get(config
				.getRefPlatform());
		checkTestBrowser(logId, browserMap, config.getRefPlatform(),
				refBrowserPages);
		LOG.info("all:" + (new Date().getTime() - start));
	}

	private void checkTestBrowser(String logId,
			Map<Platform, List<BrowserPage>> browserMap, Platform refPlatform,
			List<BrowserPage> refBrowserPages) {
		for (Platform platform : browserMap.keySet()) {
			if (platform == refPlatform)
				continue;
			else {
				checkTwoBrowser(logId, refPlatform, refBrowserPages, platform,
						browserMap.get(platform));
			}
		}
	}

	private Map<Platform, List<BrowserPage>> loadReplayLog(String logId)
			throws HttpException, IOException {
		Map<Platform, List<BrowserPage>> browserMap = new HashMap<Platform, List<BrowserPage>>();
		if (config.getRefPlatform() != null) {
			List<BrowserPage> browserPages = getLogFromServer(logId, config
					.getRefPlatform());
			if (browserPages != null && !browserPages.isEmpty()) {
				Collections.sort(browserPages, new BrowserLogComparator());
				browserMap.put(config.getRefPlatform(), browserPages);
			}
		}

		if (config.getTestPlatform() != null) {
			Platform[] testPlats = config.getTestPlatform();
			for (int i = 0; i < testPlats.length; i++) {
				List<BrowserPage> browserPages = getLogFromServer(logId,
						testPlats[i]);
				if (browserPages != null && !browserPages.isEmpty()) {
					Collections.sort(browserPages, new BrowserLogComparator());
					browserMap.put(testPlats[i], browserPages);
				}
			}
		}

		LOG.debug(browserMap.keySet().toString());
		return browserMap;
	}

	private List<BrowserPage> getLogFromServer(String logId, Platform platform)
			throws HttpException, IOException {
		String url = InternalServerConfig.getServerAddr()
				+ "/getRecordingForXBI?id=" + logId + "&browser=" + platform.getBrowser();
		List<BrowserPage> browserLogs = null;
		HttpClient httpClient = new HttpClient();
		GetMethod getMethod = new GetMethod(url);

		httpClient.executeMethod(getMethod);
		if (getMethod.getStatusCode() == 200) {
			InputStream inputStream = getMethod.getResponseBodyAsStream();
			String contentString = getContentString(inputStream);
			Gson gson = new Gson();
			browserLogs = gson.fromJson(contentString,
					new TypeToken<List<BrowserPage>>() {
					}.getType());
		}
		return browserLogs;
	}

	private String getContentString(InputStream is) {
		BufferedReader reader = new BufferedReader(new InputStreamReader(is));
		StringBuilder sb = new StringBuilder();
		String line = null;
		try {
			sb.append(reader.readLine());
			while ((line = reader.readLine()) != null) {
				sb.append("\n");
				sb.append(line);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return sb.toString();
	}

	private StateEdgeUtil loadStateMap(String testCaseId) throws HttpException,
			IOException {
		String url = InternalServerConfig.getServerAddr()
				+ "/getStateEdgeList?testCaseId=" + testCaseId;
		TestCaseStateEdge testCaseStateEdge = null;
		HttpClient httpClient = new HttpClient();
		GetMethod getMethod = new GetMethod(url);
		httpClient.executeMethod(getMethod);
		if (getMethod.getStatusCode() == 200) {
			InputStream is = getMethod.getResponseBodyAsStream();
			String contentString = getContentString(is);
			Gson gson = new Gson();
			testCaseStateEdge = gson.fromJson(contentString,
					new TypeToken<TestCaseStateEdge>() {
					}.getType());
		}
		StateEdgeUtil seUtil = new StateEdgeUtil();
		if (null != testCaseStateEdge) {
			for (StateEdge se : testCaseStateEdge.getStateEdgeList()) {
				seUtil.pushStateEdge(se);
			}
		}
		return seUtil;
	}

	private void calResultSize(AnalysisResult results){
		
		for(PlatformXbi xbi : results.getXbi()){
			int total = 0;
			for(StateIssue stateIssue:xbi.getStateIssueList()){
				int size = stateIssue.getIssueList().size();
				stateIssue.setSize(size);
				total += size;
			}
			xbi.setSize(total);
		};
	}
	private void uploadXBIResult(AnalysisResult results) {
		calResultSize(results);
		String url = InternalServerConfig.getServerAddr() + "/saveXBI";
		try {
			org.apache.http.client.HttpClient httpClient = HttpClientBuilder
					.create().build();
			HttpPost request = new HttpPost(url);
			Gson gson = new Gson();
			String rs = gson.toJson(results);
			StringEntity s;
			s = new StringEntity(rs);

			s.setContentEncoding("UTF-8");
			s.setContentType("application/json");
			request.setEntity(s);
			try {
				HttpResponse response = httpClient.execute(request);
				if (response.getStatusLine().getStatusCode() == 200) {
					System.out.println("post success");
				} else if (response.getStatusLine().getStatusCode() == 400) {
					System.out.println("post fail");
				}
			} catch (ClientProtocolException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * check two browser log and report diff issues
	 * 
	 * @param logId
	 * @param refPlatform
	 * @param refBrowserPages
	 * @param testPlatfrom
	 * @param testBrowserPages
	 */
	private void checkTwoBrowser(String logId, Platform refPlatform,
			List<BrowserPage> refBrowserPages, Platform testPlatfrom,
			List<BrowserPage> testBrowserPages) {
		// check state
		double start = new Date().getTime();
		IsoChecker ic = new IsoChecker(new BrowserModel(refPlatform.toString(),
				refBrowserPages), new BrowserModel(testPlatfrom.toString(), testBrowserPages));
		ic.checkPages();

		XBIResult xbiResult = ic.getResults();

		Map<Integer, StateIssue> siList = xbiResult.getStateIssues();
		PlatformXbi xbi = platXbiMap.get(testPlatfrom);
		if(xbi == null){
			xbi = new PlatformXbi(testPlatfrom);
			platXbiMap.put(testPlatfrom, xbi);
		}
		xbi.addSateIssueList(siList.values());
		
		LOG.info(String.valueOf(new Date().getTime() - start));

		ErrorReporter er = new ErrorReporter(xbiResult);
		er.save(DirInfo.getOutput() + logId, refPlatform.toString(), testPlatfrom.toString());
	}
}
